﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Media;
using System.Xml.Serialization;
using System.Windows;
using Rage.Base.Abilities;
using Rage.Base;

namespace Rage.Marauder {
    public sealed class CollectedCharacterMarauder : CollectedCharacter
    {
        public CollectedCharacterMarauder() { }
        //public DefendModel DefendModel { get; set; }
        //public AttackModel AttackModel { get; set; }
        private CalculationOptionsMarauder _calcOpts;
        public new CalculationOptionsMarauder CalcOpts
        {
            get { return _calcOpts; }
            set { _calcOpts = value; }
        }
        private MarauderTalents _talentsMarauder;
        public new MarauderTalents Talents
        {
            get { return _talentsMarauder; }
            set { _talentsMarauder = value; }
        }
    }
    [Rage.Calculations.RageModelInfo("Marauder", "Ability_Rogue_Ambush", CharacterClass.Marauder)]
    public class CalculationsMarauder : CalculationsBase {
        #region Variables and Properties

        #region Gemming Templates
        public override List<GemmingTemplate> DefaultGemmingTemplates
        {
            get
            {
                // Relevant Mod IDs for Class

                //              "22/50-Green", "22/50-Blue", "22/50-Purple", "23/51-Tionese", "24/56-Columi", "25/58-Rakata"
                string[] armr = { "", "", "", "", "", "", };
                string[] barl = { "", "", "", "", "", "", };
                string[] hilt = { "", "", "", "", "", "", };
                string[] mods = { "", "", "", "", "", "", };
                string[] enhm = { "", "", "", "", "", "", };
                string[] clcr = { "", "", "", "", "", "", };
                string[] augm = { "", "", "", "", "", "", };

                List<GemmingTemplate> list = new List<GemmingTemplate>();
                for (int tier = 0; tier < 6; tier++)
                {
                    list.AddRange(new GemmingTemplate[]
                        {
                            CreateGemmingTemplate(tier, armr, barl, hilt, mods, enhm, clcr, augm),
                            //CreateGemmingTemplate(tier, armr, armr, glinting, delicate, keen, keen, keen),
                            //CreateGemmingTemplate(tier, armr, armr, glinting, delicate, keen, keen, keen),
                        });
                }
                return list;
            }
        }

        private const int DEFAULT_GEMMING_TIER = 3;
        private GemmingTemplate CreateGemmingTemplate(int tier,
            string[] armoring, string[] barrel, string[] hilt,
            string[] mod, string[] enhancement, string[] colorcrystal, string[] augment)
        {
            return new GemmingTemplate()
            {
                Model = "Assassin",
                Group = (new string[] { "22/50-Green", "22/50-Blue", "22/50-Purple", "23/51-Tionese", "24/56-Columi", "25/58-Rakata" })[tier],
                Enabled = (tier == DEFAULT_GEMMING_TIER),

                ArmoringId = armoring[tier],
                BarrelId = barrel[tier],
                HiltId = hilt[tier],
                ModId = mod[tier],
                EnhancementId = enhancement[tier],
                ColorCrystalId = colorcrystal[tier],
                AugmentId = augment[tier],

            };
        }
        private static void fixArray(int[] thearray) {
            if (thearray[0] == 0) return; // Nothing to do, they are all 0
            if (thearray[1] == 0) thearray[1] = thearray[0]; // There was a Green Rarity, but no Blue Rarity
            if (thearray[2] == 0) thearray[2] = thearray[1]; // There was a Blue Rarity (or Green Rarity as set above), but no Purple Rarity
            if (thearray[3] == 0) thearray[3] = thearray[2]; // There was a Purple Rarity (or Blue Rarity/Green Rarity as set above), but no Jewel
        }
        #endregion

        public override string GetCharacterStatsString(Character character)
        {
            if (character == null) { return ""; }
            StringBuilder stats = new StringBuilder();
            stats.AppendFormat("Character:\t\t{0}@{1}-{2}\r\nRace:\t\t{3}",
                character.Name, character.Region, character.Realm, character.Race);

            char[] splits = {':','*'};
            Dictionary<string,string> dict = GetCharacterCalculations(character, null, false, false, true).GetAsynchronousCharacterDisplayCalculationValues();
            foreach (string s in CharacterDisplayCalculationLabels)
            {
                string[] label = s.Split(splits);
                if (dict.ContainsKey(label[1]))
                {
                    stats.AppendFormat("\r\n{0}:\t\t{1}", label[1], dict[label[1]].Split('*')[0]);
                }
            }
            
            return stats.ToString();
        }
        private string[] _characterDisplayCalculationLabels = null;
        public override string[] CharacterDisplayCalculationLabels {
            get {
                if (_characterDisplayCalculationLabels == null) {
                    _characterDisplayCalculationLabels = new string[] {
#region Base Stats
"Base Stats:Health and Stamina",
"Base Stats:Armor",
"Base Stats:Strength",
"Base Stats:Attack Power",
"Base Stats:Agility",
"Base Stats:Crit",
@"Base Stats:Hit*8.00% chance to miss base for Yellow Attacks (LVL 83 Targ)
Precision 0- 8%-0%=8%=264 Rating soft cap
Precision 1- 8%-1%=7%=230 Rating soft cap
Precision 2- 8%-2%=6%=197 Rating soft cap
Precision 3- 8%-3%=5%=164 Rating soft cap
NOTICE: These ratings numbers will be out of date for Cataclysm",
@"Base Stats:Expertise*Base 6.50% chance to be Dodged (LVL 83 Targ)
X Axis is Weapon Mastery
Y Axis is Strength of Arms
x>| 0  |  1  |  2
0 |213|180|147
1 |197|164|131
2 |180|147|115

0/2 in each the cap is 213 Rating
2/2 in each the cap is 115 Rating

Base 13.75% chance to be Parried (LVL 83 Targ)
Strength of Arms
0 |459
1 |443
2 |426

These numbers to do not include racial bonuses.
NOTICE: These ratings numbers will be out of date for Cataclysm",
"Base Stats:Haste",
"Base Stats:Armor Penetration*Cataclysm no longer has ArP Rating but you can still get ArP % from Talents and Abilities",
"Base Stats:Mastery",
#endregion
            
#region Fury
@"DPS Breakdown (Fury):Description 1*1st Number is per second or per tick
2nd Number is the average damage (factoring mitigation, hit/miss ratio and crits) per hit
3rd Number is number of times activated over fight duration",
"DPS Breakdown (Fury):Bloodsurge",
"DPS Breakdown (Fury):Bloodthirst",
"DPS Breakdown (Fury):Whirlwind",
"DPS Breakdown (Fury):Raging Blow",
#endregion

#region Arms
@"DPS Breakdown (Arms):Description 2*1st Number is per second or per tick
2nd Number is the average damage (factoring mitigation, hit/miss ratio and crits) per hit
3rd Number is number of times activated over fight duration",
"DPS Breakdown (Arms):Shattering Throw",
"DPS Breakdown (Arms):Bladestorm*Bladestorm only uses 1 GCD to activate but it is channeled for a total of 4 GCD's",
"DPS Breakdown (Arms):Mortal Strike",
"DPS Breakdown (Arms):Rend*The Blood and Thunder Talent can refresh Rend so you will only see approximately one activate but you'll still see the full Rend DPS here.",
"DPS Breakdown (Arms):Thunder Clap",
"DPS Breakdown (Arms):Taste for Blood*Perform an Overpower",
"DPS Breakdown (Arms):Overpower",
"DPS Breakdown (Arms):Slam*If this number is zero, it most likely means that your other abilities are proc'g often enough that you are rarely, if ever, having to resort to Slamming your target.",
"DPS Breakdown (Arms):Strikes Of Opportunity*The Arms Mastery Rating based ability",
#endregion

#region Shared
@"DPS Breakdown (Shared):Description 3*1st Number is per second or per tick
2nd Number is the average damage (factoring mitigation, hit/miss ratio and crits) per hit
3rd Number is number of times activated over fight duration",
"DPS Breakdown (Shared):Heroic Throw*" +
@"If you are Glyphed, this will apply your first Sunder Armor stack
If you have Moves set up in the Boss Handler that are more than
a GCD's length, you will use this while running back into place",
"DPS Breakdown (Shared):Colossus Smash*This ability provides the ArP % you see above",
"DPS Breakdown (Shared):Victory Rush*Slam will override this if it does more damage",
"DPS Breakdown (Shared):Heroic Strike",
"DPS Breakdown (Shared):Cleave",
"DPS Breakdown (Shared):Execute*<20% Spamming only",
"DPS Breakdown (Shared):Deep Wounds",
#endregion

#region General
"DPS Breakdown (General):White DPS",
"DPS Breakdown (General):Special DMG Procs*Such as Bandit's Insignia, Hand Mounted Pyro Rocket or Goblin Rocket Belt",
@"DPS Breakdown (General):Total DPS*1st number is total DPS
2nd number is total DMG over Duration",
#endregion

#region Rage Details
"Rage Details:Description 4",
"Rage Details:Rage Above 20%",
"Rage Details:Rage Below 20%",
#endregion

                    };
                }
                return _characterDisplayCalculationLabels;
            }
        }

        private string[] _optimizableCalculationLabels = null;
        public override string[] OptimizableCalculationLabels {
            get {
                if (_optimizableCalculationLabels == null)
                    _optimizableCalculationLabels = new string[] {
                        "Health",
                        "Armor",
                        "Strength",
                        "Attack Power",
                        "Agility",
                        "Crit %",
                        "Haste %",
                        "% Chance to Miss (White)",
                        "% Chance to Miss (Yellow)",
                        "% Chance to be Dodged",
                        "% Chance to be Parried",
                        "% Chance to be Avoided (Yellow/Dodge)",
                    };
                return _optimizableCalculationLabels;
            }
        }

        private Dictionary<string, Color> _subPointNameColors = null;
        public override Dictionary<string, Color> SubPointNameColors {
            get {
                if (_subPointNameColors == null) {
                    _subPointNameColors = new Dictionary<string, Color>();
                    _subPointNameColors.Add("DPS", Colors.Red);
                    _subPointNameColors.Add("Survivability", Colors.Green);
                }
                return _subPointNameColors;
            }
        }

        public override CharacterClass TargetClass { get { return CharacterClass.Marauder; } }
        public override ComparisonCalculationBase CreateNewComparisonCalculation() { return new ComparisonCalculationMarauder(); }
        public override CharacterCalculationsBase CreateNewCharacterCalculations() { return new CharacterCalculationsMarauder(); }
        public ICalculationOptionsPanel _calculationOptionsPanel = null;
        public override ICalculationOptionsPanel CalculationOptionsPanel { get { return _calculationOptionsPanel ?? (_calculationOptionsPanel = new CalculationOptionsPanelMarauder()); } }

        public override ICalculationOptionBase DeserializeDataObject(string xml) {
            CalculationOptionsMarauder calcOpts = null;
            StringReader sr = null;
            try
            {
                XmlSerializer s = new XmlSerializer(typeof(CalculationOptionsMarauder));
                sr = new StringReader(xml);
                calcOpts = s.Deserialize(sr) as CalculationOptionsMarauder;
            }
            finally { sr.Dispose(); }
            return calcOpts;
        }

        // cached special effects
        private static readonly SpecialEffect _SE_4T11 = new SpecialEffect(Trigger.OPAttack, new Stats() { BonusPowerMultiplier = 0.01f, }, 30, 0, 1f, 3);
        #endregion

        #region Relevancy

        private List<ItemType> _relevantItemTypes = null;
        public override List<ItemType> RelevantItemTypes {
            get {
                if (_relevantItemTypes == null) {
                    _relevantItemTypes = new List<ItemType>(new[] {
                        ItemType.None,
                        ItemType.Heavy,
                        ItemType.BlasterRifle, ItemType.AssaultCannon, ItemType.SniperRifle, ItemType.Shotgun,
                        ItemType.Vibroknife, ItemType.Lightsaber,
                        ItemType.Electrostaff,
                        ItemType.Techblade, ItemType.Vibrosword,
                    });
                }
                return _relevantItemTypes;
            }
        }

        public override bool ItemFitsInSlot(Item item, Character character, CharacterSlot slot, bool ignoreUnique) {
            // We need specialized handling due to Titan's Grip and SMT
            if (item == null || character == null) {
                return false;
            }

            if (character.MarauderTalents.Annihilate > 0) {
                // If it's a 1h weapon it can go in OH or MH
                if (item.Slot == ItemSlot.OneHand && (slot == CharacterSlot.MainHand || slot == CharacterSlot.OffHand)) return true;
                // If it's a MH weapon it can go in MH only
                if (item.Slot == ItemSlot.MainHand && slot == CharacterSlot.MainHand) return true;
                // If it's an OH weapon it can go in OH only
                if (item.Slot == ItemSlot.OffHand && slot == CharacterSlot.OffHand) return true;
            }

            return base.ItemFitsInSlot(item, character, slot, ignoreUnique);
        }

        private static List<string> _relevantGlyphs = null;
        public override List<string> GetRelevantGlyphs() {
            if (_relevantGlyphs == null) {
                _relevantGlyphs = new List<string>() {
                    #region Prime
                    "Glyph of Bladestorm", //@"Reduces the cooldown on Bladestorm by 15 sec."
                    "Glyph of Bloodthirst", //@"Increases the healing your receive from Bloodthirst ability by 100%."
                    //"Glyph of Devastate", //@"Your Devastate ability now applies two stacks of Sunder Armor."
                    "Glyph of Mortal Strike", //@"Increases the damage of your Mortal Strike ability by 10%."
                    "Glyph of Overpower", //@"Adds a 100% chance to enable your Overpower when your attacks are parried."
                    "Glyph of Raging Blow", //@"Increases the critical strike chance of Raging Blow by 5%."
                    //"Glyph of Revenge", //@"After using Revenge, your next Heroic Strike costs no rage."
                    //"Glyph of Shield Slam", //@"Increases the damage of your Shield Slam by 10%."
                    "Glyph of Slam", //@"Increases the critical strike chance of Slam by 5%."
                    #endregion
                    #region Major
                    "Glyph of Cleaving", // Increases the number of targets your Cleave hits by 1.
                    "Glyph of Colossus Smash", // Your Colossus Smash refreshes the duration of Sunder Armor stacks on a target.
                    "Glyph of Death Wish", // Death Wish no longer increases damage taken.
                    "Glyph of Heroic Throw", // Your Heroic Throw applies a stack of Sunder Armor.
                    "Glyph of Intercept", // Increases the duration of your Intercept stun by 1 sec.
                    //"Glyph of Intervene", // Increases the number of attacks you intercept for your Intervene target by 1.
                    "Glyph of Long Charge", // Increases the range of your Charge ability by 5 yards.
                    "Glyph of Piercing Howl", // Increases the radius of Piercing Howl by 50%.
                    "Glyph of Rapid Charge", // Reduces the cooldown of your Charge ability by 7%.
                    "Glyph of Resonating Power", // Reduces the rage cost of your Thunder Clap ability by 5.
                    //"Glyph of Shield Wall", // Shield wall now reduces damage taken by 20%, but increases its cooldown by 2 min.
                    //"Glyph of Shockwave", // Reduces the cooldown on Shockwave by 3 sec.
                    //"Glyph of Spell Reflection", // Reduces the cooldown on Spell Reflection by 1 sec.
                    //"Glyph of Sunder Armor", // Your Sunder Armor ability effects a second nearby target.
                    "Glyph of Sweeping Strikes", // Reduces the rage cost of Sweeping Strikes ability by 100%.
                    "Glyph of Thunder Clap", // Increases the radius of your Thunder Clap ability by 2 yards.
                    "Glyph of Victory Rush", // Increases the total healing provided by your Victory Rush by 50%.
                    #endregion
                    #region Minor
                    "Glyph of Battle", //@"Increases the duration of your Battle Shout by 2 min.")]
                    "Glyph of Berserker Rage", //@"Berserker Rage generates 5 rage when used.")]
                    "Glyph of Bloody Healing", //@"Increases the healing your recieve from your Bloodthirst ability by 100%.")]
                    "Glyph of Command", //@"Increases the duration by 2 min and the area of effect by 50% of your Commanding Shout.")]
                    "Glyph of Demoralizing Shout", //@"Increases the duration by 15 sec and area of effect 50% of your Demoralizing Shout.")]
                    "Glyph of Enduring Victory", //@"Increases the window of opportunity in which you can use Victory Rush by 5 sec.")]
                    "Glyph of Furious Sundering", //@"Reduces the cost of Sunder Armor by 50%.")]
                    "Glyph of Intimidating Shout", //@"Targets of your Intimidating Shout no longer move faster when feared.")]
                    #endregion
                };
            }
            return _relevantGlyphs;
        }

        private static bool HidingBadStuff { get { return HidingBadStuff_Def || HidingBadStuff_Spl || HidingBadStuff_PvP; } }
        internal static bool HidingBadStuff_Def { get; set; }
        internal static bool HidingBadStuff_Spl { get; set; }
        internal static bool HidingBadStuff_PvP { get; set; }

        internal static List<Trigger> _RelevantTriggers = null;
        internal static List<Trigger> RelevantTriggers {
            get {
                return _RelevantTriggers ?? (_RelevantTriggers = new List<Trigger>() {
                    // General
                    Trigger.Use,
                    Trigger.MeleeAttack,
                    Trigger.MeleeCrit,
                    Trigger.MeleeHit,
                    Trigger.MainHandHit,
                    Trigger.CurrentHandHit,
                    Trigger.PhysicalCrit,
                    Trigger.PhysicalHit,
                    Trigger.PhysicalAttack,
                    Trigger.DoTTick,
                    Trigger.DamageDone,
                    Trigger.DamageTaken,
                    Trigger.DamageAvoided,
                    Trigger.DamageOrHealingDone,
                    // Special
                    Trigger.DarkIntentCriticalPeriodicDamageOrHealing,
                    // Warrior Specific
                    Trigger.HSorSLHit,
                    Trigger.ColossusSmashHit,
                    Trigger.ExecuteHit,
                    Trigger.OPAttack,
                    Trigger.MortalStrikeCrit,
                    Trigger.MortalStrikeHit,
                    Trigger.BattleorCommandingShout,
                });
            }
        }

        public override Stats GetRelevantStats(Stats stats) {
            if (stats == null) { return new Stats(); }
            Stats relevantStats = new Stats() {
                #region Wanted Stats
                // Base Stats
                Strength = stats.Strength,
                Cunning = stats.Cunning,
                Power = stats.Power,
                // Ratings
                CritRating = stats.CritRating,
                AccuracyRating = stats.AccuracyRating,
                AlacrityRating = stats.AlacrityRating,
                ExpertiseRating = stats.ExpertiseRating,
                // Bonuses
                ArmorPenetration = stats.ArmorPenetration,
                TargetArmorReduction = stats.TargetArmorReduction,
                //PhysicalCrit = stats.PhysicalCrit,
                Alacrity = stats.Alacrity,
                //PhysicalHit = stats.PhysicalHit,
                //SpellHit = stats.SpellHit,
                // Boss Handler
                MovementSpeed = stats.MovementSpeed,
                StunDurReduc = stats.StunDurReduc,
                SnareRootDurReduc = stats.SnareRootDurReduc,
                FearDurReduc = stats.FearDurReduc,
                DisarmDurReduc = stats.DisarmDurReduc,
                // Target Debuffs
                BossAttackSpeedReductionMultiplier = stats.BossAttackSpeedReductionMultiplier,
                // Procs
                ForceorEquivRestore = stats.ForceorEquivRestore,
                // Damage Procs
                KineticDamage = stats.KineticDamage,
                EnergyDamage = stats.EnergyDamage,
                InternalDamage = stats.InternalDamage,
                ElementalDamage = stats.ElementalDamage,
                BonusKineticDamageMultiplier = stats.BonusKineticDamageMultiplier,
                BonusEnergyDamageMultiplier = stats.BonusEnergyDamageMultiplier,
                BonusInternalDamageMultiplier = stats.BonusInternalDamageMultiplier,
                BonusElementalDamageMultiplier = stats.BonusElementalDamageMultiplier,
                // Multipliers
                BonusCunningMultiplier = stats.BonusCunningMultiplier,
                BonusStrengthMultiplier = stats.BonusStrengthMultiplier,
                BonusPowerMultiplier = stats.BonusPowerMultiplier,
                BonusBleedDamageMultiplier = stats.BonusBleedDamageMultiplier,
                BonusDamageMultiplier = stats.BonusDamageMultiplier,
                BonusSurgeRatingMultiplier = stats.BonusSurgeRatingMultiplier,
                BonusCritChance = stats.BonusCritChance,
                BonusPeriodicDamageMultiplier = stats.BonusPeriodicDamageMultiplier,
                // Set Bonuses
                // Special
                BonusRageGen = stats.BonusRageGen,
                #endregion
                #region Survivability Stats
                Endurance = stats.Endurance,
                Health = stats.Health,
                BonusEnduranceMultiplier = stats.BonusEnduranceMultiplier,
                BonusHealthMultiplier = stats.BonusHealthMultiplier,
                HealthRestore = stats.HealthRestore,
                HealthRestoreFromMaxHealth = stats.HealthRestoreFromMaxHealth,
                Armor = stats.Armor,
                BonusArmorMultiplier = stats.BonusArmorMultiplier,
                DamageTakenReductionMultiplier = stats.DamageTakenReductionMultiplier,
                BossPhysicalDamageDealtReductionMultiplier = stats.BossPhysicalDamageDealtReductionMultiplier,
                BonusHealingDoneMultiplier = stats.BonusHealingDoneMultiplier, // not really rel but want it if it's available on something else
                #endregion
            };
            foreach (SpecialEffect effect in stats.SpecialEffects()) {
                if (RelevantTriggers.Contains(effect.Trigger) && (HasRelevantStats(effect.Stats) || HasSurvivabilityStats(effect.Stats)))
                {
                    relevantStats.AddSpecialEffect(effect);
                }
            }
            return relevantStats;
        }
        public override bool HasRelevantStats(Stats stats) {
            bool relevant = HasWantedStats(stats) && !HasIgnoreStats(stats);
            return relevant;
        }

        private bool HasWantedStats(Stats stats) {
            // Base Stats
            if (stats.Strength != 0) { return true; }
            if (stats.Cunning != 0) { return true; }
            if (stats.Power != 0) { return true; }
            // Ratings
            if (stats.CritRating != 0) { return true; }
            if (stats.AccuracyRating != 0) { return true; }
            if (stats.AlacrityRating != 0) { return true; }
            if (stats.ExpertiseRating != 0) { return true; }
            // Bonuses
            if (stats.ArmorPenetration != 0) { return true; }
            if (stats.TargetArmorReduction != 0) { return true; }
            //if (stats.PhysicalCrit != 0) { return true; }
            if (stats.Alacrity != 0) { return true; }
            //if (stats.PhysicalHit != 0) { return true; }
            //if (stats.SpellHit != 0) { return true; } // used for TClap/Demo Shout maintenance
            // Boss Handler
            if (stats.MovementSpeed != 0) { return true; }
            if (stats.StunDurReduc != 0) { return true; }
            if (stats.SnareRootDurReduc != 0) { return true; }
            if (stats.FearDurReduc != 0) { return true; }
            if (stats.DisarmDurReduc != 0) { return true; }
            // Target Debuffs
            if (stats.BossAttackSpeedReductionMultiplier != 0) { return true; }
            // Procs
            if (stats.ForceorEquivRestore != 0) { return true; }
            // Damage Procs
            if (stats.KineticDamage != 0) { return true; }
            if (stats.EnergyDamage != 0) { return true; }
            if (stats.InternalDamage != 0) { return true; }
            if (stats.ElementalDamage != 0) { return true; }
            if (stats.BonusKineticDamageMultiplier != 0) { return true; }
            if (stats.BonusEnergyDamageMultiplier != 0) { return true; }
            if (stats.BonusInternalDamageMultiplier != 0) { return true; }
            if (stats.BonusElementalDamageMultiplier != 0) { return true; }
            // Multipliers
            if (stats.BonusCunningMultiplier != 0) { return true; }
            if (stats.BonusStrengthMultiplier != 0) { return true; }
            if (stats.BonusPowerMultiplier != 0) { return true; }
            if (stats.BonusBleedDamageMultiplier != 0) { return true; }
            if (stats.BonusDamageMultiplier != 0) { return true; }
            if (stats.BonusSurgeRatingMultiplier != 0) { return true; }
            if (stats.BonusCritChance != 0) { return true; }
            if (stats.BonusPeriodicDamageMultiplier != 0) { return true; }
            // Set Bonuses
            // Special
            if (stats.BonusRageGen != 0) { return true; }
            // Special Effects
            foreach (SpecialEffect effect in stats.SpecialEffects()) {
                if (RelevantTriggers.Contains(effect.Trigger) && HasRelevantStats(effect.Stats)) {
                    return true;
                }
            }
            return false;
        }

        private bool HasSurvivabilityStats(Stats stats) {
            // Health Base
            if (stats.Endurance != 0) { return true; }
            if (stats.Health != 0) { return true; }
            if (stats.BonusEnduranceMultiplier != 0) { return true; }
            if (stats.BonusHealthMultiplier != 0) { return true; }
            // Health Regen
            if (stats.HealthRestore != 0) { return true; }
            if (stats.HealthRestoreFromMaxHealth != 0) { return true; }
            // Armor
            if (stats.Armor != 0) { return true; }
            if (stats.BonusArmorMultiplier != 0) { return true; }
            // Multipliers
            if (stats.DamageTakenReductionMultiplier != 0) { return true; }
            // Target Debuffs
            if (stats.BossPhysicalDamageDealtReductionMultiplier != 0) { return true; }
            // Special Effects
            foreach (SpecialEffect effect in stats.SpecialEffects()) {
                if (RelevantTriggers.Contains(effect.Trigger) && HasSurvivabilityStats(effect.Stats)) {
                    return true;
                }
            }
            return false;
        }

        private bool HasIgnoreStats(Stats stats) {
            if (!HidingBadStuff) { return false; }

            // Remove Spellcasting Stuff
            if (HidingBadStuff_Spl) {
                if (stats.Fp5 != 0) { return true; }
                if (stats.Power != 0) { return true; }
                if (stats.Force != 0) { return true; }
                if (stats.ForceRestore != 0) { return true; }
                if (stats.Aim != 0) { return true; }
                if (stats.Willpower != 0) { return true; }
                if (stats.BonusAimMultiplier != 0) { return true; }
                if (stats.BonusWillpowerMultiplier != 0) { return true; }
                if (stats.BonusForceMultiplier != 0) { return true; }
            }
            // Remove Defensive Stuff
            if (HidingBadStuff_Def) {
                if (stats.Dodge != 0) { return true; }
                if (stats.Parry != 0) { return true; }
                if (stats.DefenseRating != 0) { return true; }
                if (stats.ShieldRating != 0) { return true; }
                if (stats.AbsorbRating != 0) { return true; }
                if (stats.Shield != 0) { return true; }
                if (stats.SpellReflectChance != 0) { return true; }
            }
            // Remove PvP Items
            if (HidingBadStuff_PvP) {
            }
            // Special Effects
            foreach (SpecialEffect effect in stats.SpecialEffects()) {
                // The effect doesn't have a relevant trigger
                if (!RelevantTriggers.Contains(effect.Trigger)) { return true; }
                // The Effect has Ignore Stats
                if (HasIgnoreStats(effect.Stats)) { return true; }
            }
            return false;
        }

        public override bool IsItemRelevant(Item item) {
            if (item == null) { return false; }
            /*if ( // Manual override for +X to all Stats gems
                   item.Id == 49110 // Nightmare Tear
                || item.Id == 42702 // Enchanted Tear
                || item.Id == 42701 // Enchanted Pearl
                || item.Id == 34143 // Chromatic Sphere
                ) {
                return true;
                //}else if (item.Type == ItemType.Polearm && 
            } else*/ {
                Stats stats = item.Stats;
                bool wantedStats = HasWantedStats(stats);
                bool survstats = HasSurvivabilityStats(stats);
                bool ignoreStats = HasIgnoreStats(stats);
                return (wantedStats || survstats) && !ignoreStats && base.IsItemRelevant(item);
            }
        }

        public override bool IsBuffRelevant(Buff buff, Character character) {
            if (buff == null) { return false; }
            // Force some buffs to active
            if (buff.SpellId == 53909 // Wild Magic Potion
                || buff.SpellId == 28494 // Insane Strength Potion
                || buff.SpellId == 22738 // Disarm Duration Reduction (Old PvP Set Bonuses)
                || buff.SpellId == 90293  // T11 2P
                || buff.SpellId == 90295  // T11 4P
                || buff.SpellId == 99233  // T12 2P
                || buff.SpellId == 99237) // T12 4P
            { return true; }
            // Force some buffs to go away
            else if (!buff.AllowedClasses.Contains(CharacterClass.Marauder))
            { return false; }
            //
            bool haswantedStats = HasWantedStats(buff.Stats);
            bool hassurvStats = HasSurvivabilityStats(buff.Stats);
            bool hasbadstats = HasIgnoreStats(buff.Stats);
            bool retVal = haswantedStats || (hassurvStats && !hasbadstats);
            return retVal;
        }
        public Base.StatsWarrior GetBuffsStats(CollectedCharacter dpswarchar)
        {
            List<Buff> removedBuffs = new List<Buff>();
            List<Buff> addedBuffs = new List<Buff>();

            List<Buff> buffGroup = new List<Buff>();
            #region Maintenance Auto-Fixing
            // Removes the Sunder Armor if you are maintaining it yourself
            // Also removes Acid Spit and Expose Armor
            // We are now calculating this internally for better accuracy and to provide value to relevant talents
            /*if (dpswarchar.CalcOpts.M_SunderArmor) {
                buffGroup.Clear();
                buffGroup.Add(Buff.GetBuffBySpellId(7386));//Buff.GetBuffByName("Sunder Armor"));
                buffGroup.Add(Buff.GetBuffBySpellId(8647));//Buff.GetBuffByName("Expose Armor"));
                buffGroup.Add(Buff.GetBuffBySpellId(770));//Buff.GetBuffByName("Faerie Fire"));
                buffGroup.Add(Buff.GetBuffBySpellId(35387));//Buff.GetBuffByName("Corrosive Spit"));
                buffGroup.Add(Buff.GetBuffBySpellId(50498));//Buff.GetBuffByName("Tear Armor"));
                MaintBuffHelper(buffGroup, dpswarchar.Char, removedBuffs);
            }*/

            // Removes the Shattering Throw Buff if you are maintaining it yourself
            // We are now calculating this internally for better accuracy and to provide value to relevant talents
            /*if (dpswarchar.CalcOpts.M_ShatteringThrow) {
                buffGroup.Clear();
                buffGroup.Add(Buff.GetBuffBySpellId(64382));//Buff.GetBuffByName("Shattering Throw"));
                MaintBuffHelper(buffGroup, dpswarchar.Char, removedBuffs);
            }*/

            // Removes the Thunder Clap & Improved Buffs if you are maintaining it yourself
            // Also removes Judgements of the Just, Infected Wounds, Frost Fever, Improved Icy Touch
            // We are now calculating this internally for better accuracy and to provide value to relevant talents
            /*if (dpswarchar.CalcOpts.M_ThunderClap) {
                buffGroup.Clear();
                buffGroup.Add(Buff.GetBuffBySpellId(6343));//Buff.GetBuffByName("Thunder Clap"));
                buffGroup.Add(Buff.GetBuffBySpellId(59921));//Buff.GetBuffByName("Frost Fever"));
                buffGroup.Add(Buff.GetBuffBySpellId(53696));//Buff.GetBuffByName("Judgements of the Just"));
                buffGroup.Add(Buff.GetBuffBySpellId(48484));//Buff.GetBuffByName("Infected Wounds"));
                MaintBuffHelper(buffGroup, dpswarchar.Char, removedBuffs);
            }*/

            // Removes the Demoralizing Shout & Improved Buffs if you are maintaining it yourself
            // We are now calculating this internally for better accuracy and to provide value to relevant talents
            /*if (dpswarchar.CalcOpts.M_DemoralizingShout) {
                buffGroup.Clear();
                buffGroup.Add(Buff.GetBuffBySpellId(1160));//Buff.GetBuffByName("Demoralizing Shout"));
                buffGroup.Add(Buff.GetBuffBySpellId(99));//Buff.GetBuffByName("Demoralizing Roar"));
                buffGroup.Add(Buff.GetBuffBySpellId(26016));//Buff.GetBuffByName("Vindication"));
                buffGroup.Add(Buff.GetBuffBySpellId(702));//Buff.GetBuffByName("Curse of Weakness"));
                buffGroup.Add(Buff.GetBuffBySpellId(81132));//Buff.GetBuffByName("Scarlet Fever"));
                MaintBuffHelper(buffGroup, dpswarchar.Char, removedBuffs);
            }*/

            // Removes the Battle Shout & Commanding Presence Buffs if you are maintaining it yourself
            // Also removes their equivalent of Blessing of Might (+Improved)
            // We are now calculating this internally for better accuracy and to provide value to relevant talents
            /*if (dpswarchar.CalcOpts.M_BattleShout) {
                buffGroup.Clear();
                buffGroup.Add(Buff.GetBuffBySpellId(6673));//Buff.GetBuffByName("Battle Shout"));
                buffGroup.Add(Buff.GetBuffBySpellId(8075));//Buff.GetBuffByName("Strength of Earth Totem"));
                buffGroup.Add(Buff.GetBuffBySpellId(57730));//Buff.GetBuffByName("Horn of Winter"));
                buffGroup.Add(Buff.GetBuffBySpellId(93435));//Buff.GetBuffByName("Roar of Courage"));
                MaintBuffHelper(buffGroup, dpswarchar.Char, removedBuffs);
            }*/

            // Removes the Commanding Shout & Commanding Presence Buffs if you are maintaining it yourself
            // Also removes their equivalent of Blood Pact (+Improved Imp)
            // We are now calculating this internally for better accuracy and to provide value to relevant talents
            /*if (dpswarchar.CalcOpts.M_CommandingShout) {
                buffGroup.Clear();
                buffGroup.Add(Buff.GetBuffBySpellId(469));//Buff.GetBuffByName("Commanding Shout"));
                buffGroup.Add(Buff.GetBuffBySpellId(21562));//Buff.GetBuffByName("Power Word: Fortitude"));
                buffGroup.Add(Buff.GetBuffBySpellId(90364));//Buff.GetBuffByName("Quiraji Fortitude"));
                buffGroup.Add(Buff.GetBuffBySpellId(6307));//Buff.GetBuffByName("Blood Pact"));
                MaintBuffHelper(buffGroup, dpswarchar.Char, removedBuffs);
            }*/
            #endregion

            #region Passive Ability Auto-Fixing
            // Removes the Blood Frenzy Buff and it's equivalent of Savage Combat if you are maintaining it yourself
            // Cata also has BF giving what Trauma used to
            // We are now calculating this internally for better accuracy and to provide value to relevant talents
            if (dpswarchar.Char.MarauderTalents.Annihilate > 0)
            {
                buffGroup.Clear();
                buffGroup.Add(Buff.GetBuffBySpellId(29859));//Buff.GetBuffByName("Blood Frenzy (Bleed)"));
                buffGroup.Add(Buff.GetBuffBySpellId(33876));//Buff.GetBuffByName("Mangle"));
                buffGroup.Add(Buff.GetBuffBySpellId(16511));//Buff.GetBuffByName("Hemorrhage"));
                buffGroup.Add(Buff.GetBuffBySpellId(50271));//Buff.GetBuffByName("Tendon Rip"));
                buffGroup.Add(Buff.GetBuffBySpellId(35290));//Buff.GetBuffByName("Gore"));
                buffGroup.Add(Buff.GetBuffBySpellId(57386));//Buff.GetBuffByName("Stampede"));
                //
                buffGroup.Add(Buff.GetBuffBySpellId(29859*10));//Buff.GetBuffByName("Blood Frenzy (Vuln)"));
                buffGroup.Add(Buff.GetBuffBySpellId(58413));//Buff.GetBuffByName("Savage Combat"));
                buffGroup.Add(Buff.GetBuffBySpellId(81328));//Buff.GetBuffByName("Brittle Bones"));
                buffGroup.Add(Buff.GetBuffBySpellId(50518));//Buff.GetBuffByName("Ravage"));
                buffGroup.Add(Buff.GetBuffBySpellId(55749));//Buff.GetBuffByName("Acid Spit"));
                MaintBuffHelper(buffGroup, dpswarchar.Char, removedBuffs);
            }

            // Removes the Rampage Buff and it's equivalent of Leader of the Pack if you are maintaining it yourself
            // We are now calculating this internally for better accuracy and to provide value to relevant talents
            if (dpswarchar.Char.MarauderTalents.Annihilate > 0)
            {
                buffGroup.Clear();
                buffGroup.Add(Buff.GetBuffBySpellId(29801));//Buff.GetBuffByName("Rampage"));
                buffGroup.Add(Buff.GetBuffBySpellId(17007));//Buff.GetBuffByName("Leader of the Pack"));
                buffGroup.Add(Buff.GetBuffBySpellId(51701));//Buff.GetBuffByName("Honor Among Thieves"));
                buffGroup.Add(Buff.GetBuffBySpellId(51470));//Buff.GetBuffByName("Elemental Oath"));
                buffGroup.Add(Buff.GetBuffBySpellId(24604));//Buff.GetBuffByName("Furious Howl"));
                buffGroup.Add(Buff.GetBuffBySpellId(90309));//Buff.GetBuffByName("Terrifying Roar"));
                MaintBuffHelper(buffGroup, dpswarchar.Char, removedBuffs);
            }
            #endregion

            Base.StatsWarrior statsBuffs = new Base.StatsWarrior();
            statsBuffs.Accumulate(GetBuffsStats(dpswarchar.Char.ActiveBuffs));
            AccumulateSetBonusStats(statsBuffs, dpswarchar.Char.SetBonusCount);

            int T11count;
            dpswarchar.Char.SetBonusCount.TryGetValue("Earthen Warplate", out T11count);
            if (T11count >= 2) {//dpswarchar.Char.ActiveBuffs.Find<Buff>(x => x.SpellId == 90293) != null) {
                //statsBuffs.BonusMortalStrikeDamageMultiplier = 0.05f;
                //statsBuffs.BonusBloodthirstDamageMultiplier = 0.05f;
            }
            if (T11count >= 4) {//dpswarchar.Char.ActiveBuffs.Find<Buff>(x => x.SpellId == 90295) != null) {
                statsBuffs.AddSpecialEffect(_SE_4T11);
            }
            int T12count;
            dpswarchar.Char.SetBonusCount.TryGetValue("Molten Giant Warplate", out T12count);
            if (T12count >= 2) {
                //statsBuffs.AddSpecialEffect(_SE_2T12[dpswarchar.Talents.Annihilate]);
            }
            if (T12count >= 4) {
                //statsBuffs.AddSpecialEffect(_SE_4T12);
            }
            
            foreach (Buff b in removedBuffs) { dpswarchar.Char.ActiveBuffsAdd(b); }
            foreach (Buff b in addedBuffs) { dpswarchar.Char.ActiveBuffs.Remove(b); }

            return statsBuffs;
        }
        private static void MaintBuffHelper(List<Buff> buffGroup, Character character, List<Buff> removedBuffs)
        {
            foreach (Buff b in buffGroup) {
                if (character.ActiveBuffs.Remove(b)) { removedBuffs.Add(b); }
            }
        }

        public override void SetDefaults(Character character) {
            // Need to be behind boss
            character.BossOptions.InBack = true;
            character.BossOptions.InBackPerc_Melee = 1.00d;
        }

        public override bool IncludeOffHandInCalculations(Character character) {
            if (character == null || character.OffHand == null) { return false; }
            MarauderTalents talents = (MarauderTalents)character.CurrentTalents;
            if (talents.Annihilate > 0 || talents.Annihilate > 0) {
                return true;
            } else { // if (character.MainHand.Slot != ItemSlot.TwoHand)
                return base.IncludeOffHandInCalculations(character);
            }
            //return false;
        }

        #endregion

        #region Special Comparison Charts
        private string[] _customChartNames = null;
        public override string[] CustomChartNames {
            get {
                if (_customChartNames == null) {
                    _customChartNames = new string[] {
                        "Ability DPS",
                        "Ability Damage per GCD",
                        "Rage Cost per Damage",
                        "Execute Spam",
                        "Ability Maintenance Changes",
#if DEBUG
                        "PTR Testing", // This is Devs only
#endif
                    };
                }
                return _customChartNames;
            }
        }

        float getDPS(CollectedCharacter dpswarchar, int Iter, bool with)
        {
            //bool orig = dpswarchar.CalcOpts.MaintenanceTree[Iter];
            //dpswarchar.CalcOpts.MaintenanceTree[Iter] = with;
            //CharacterCalculationsMarauder calculations = GetCharacterCalculations(dpswarchar.Char.Clone()) as CharacterCalculationsMarauder;
            //dpswarchar.CalcOpts.MaintenanceTree[Iter] = orig;
            return 0;//calculations.TotalDPS;
        }
        float getSurv(CollectedCharacter dpswarchar, int Iter, bool with)
        {
            //bool orig = dpswarchar.CalcOpts.MaintenanceTree[Iter];
            //dpswarchar.CalcOpts.MaintenanceTree[Iter] = with;
            //CharacterCalculationsMarauder calculations = GetCharacterCalculations(dpswarchar.Char.Clone()) as CharacterCalculationsMarauder;
            //dpswarchar.CalcOpts.MaintenanceTree[Iter] = orig;
            return 0;//calculations.TotalHPS;
        }

        ComparisonCalculationMarauder getComp(CollectedCharacter dpswarchar, string Name, int Iter) {
            ComparisonCalculationMarauder comparison = new ComparisonCalculationMarauder();
            comparison.Name = Name;
            comparison.Equipped = true;// dpswarchar.CalcOpts.MaintenanceTree[Iter] == true;
            float with = getDPS(dpswarchar, Iter, true);
            float without = getDPS(dpswarchar, Iter, false);
            comparison.DPSPoints = with - without;
            with = getSurv(dpswarchar, Iter, true) * 0;// dpswarchar.CalcOpts.SurvScale;
            without = getSurv(dpswarchar, Iter, false) * 0;// dpswarchar.CalcOpts.SurvScale;
            comparison.SurvPoints = with - without;
            //comparison.ImageSource = aw.ability.Icon; // TODO
            //comparison.Description = aw.ability.Desc; // TODO
            return comparison;
        }

        public override ComparisonCalculationBase[] GetCustomChartData(Character character, string chartName) {
            if (character == null) { return null;}
            Character zeOriginal = character.Clone();
            Character zeClone = character.Clone();
            CharacterCalculationsMarauder calculations = GetCharacterCalculations(zeOriginal) as CharacterCalculationsMarauder;
            CalculationOptionsMarauder calcOpts = zeOriginal.CalculationOptions as CalculationOptionsMarauder;
            ((CalculationOptionsPanelMarauder)CalculationOptionsPanel).IsLoadingCalculationOptions = true;
            bool[] origMaints = (bool[])calcOpts.MaintenanceTree.Clone();
            CollectedCharacter dpswarchar = new CollectedCharacter() {
                Char = zeOriginal,
                CalcOpts = (CalculationOptionsMarauder)zeOriginal.CalculationOptions,
                BossOpts = zeOriginal.BossOptions,
                CombatFactors = null,
                Rotation = null,
            };

            switch (chartName) {
                #region Ability DPS
                case "Ability DPS": {
                    List<ComparisonCalculationBase> comparisons = new List<ComparisonCalculationBase>();
                    foreach (AbilityWrapper aw in calculations.Rot.TheAbilityList)
                    {
                        if (aw.Ability.DamageOnUse == 0) { continue; }
                        ComparisonCalculationMarauder comparison = new ComparisonCalculationMarauder();
                        comparison.Name = aw.Ability.Name;
                        comparison.Description = aw.Ability.Desc;
                        comparison.DPSPoints = aw.AllDPS;
                        comparison.SurvPoints = aw.AllHPS;
                        comparison.ImageSource = aw.Ability.Icon;
                        comparisons.Add(comparison);
                    }
                    foreach (ComparisonCalculationMarauder comp in comparisons) {
                        comp.OverallPoints = comp.DPSPoints + comp.SurvPoints;
                    }
                    calcOpts.MaintenanceTree = origMaints;
                    ((CalculationOptionsPanelMarauder)CalculationOptionsPanel).IsLoadingCalculationOptions = false;
                    return comparisons.ToArray();
                }
                #endregion
                #region Ability Damage per GCD
                case "Ability Damage per GCD":
                    {
                        List<ComparisonCalculationBase> comparisons = new List<ComparisonCalculationBase>();
                        foreach (AbilityWrapper aw in calculations.Rot.TheAbilityList)
                        {
                            if (aw.Ability.DamageOnUse == 0) { continue; }
                            ComparisonCalculationMarauder comparison = new ComparisonCalculationMarauder();
                            comparison.Name = aw.Ability.Name;
                            comparison.Description = aw.Ability.Desc;
                            comparison.DPSPoints = (
                                (false ? 0/*((aw.Ability as Rend).TickSize * (aw.Ability as Rend).NumTicks)*/
                                : aw.Ability.DamageOnUse)) / (aw.Ability.GCDTime == 0 ? 1f : (aw.Ability.GCDTime / calculations.Rot.LatentGCD));
                            comparison.SurvPoints = aw.Ability.GetAvgHealingOnUse(aw.AllNumActivates);
                            comparison.ImageSource = aw.Ability.Icon;
                            comparisons.Add(comparison);
                        }
                        foreach (ComparisonCalculationMarauder comp in comparisons)
                        {
                            comp.OverallPoints = comp.DPSPoints + comp.SurvPoints;
                        }
                        calcOpts.MaintenanceTree = origMaints;
                        ((CalculationOptionsPanelMarauder)CalculationOptionsPanel).IsLoadingCalculationOptions = false;
                        return comparisons.ToArray();
                    }
                #endregion
                #region Ability Maintenance Changes
                case "Ability Maintenance Changes": {
                    List<ComparisonCalculationBase> comparisons = new List<ComparisonCalculationBase>();
                    #region Rage Generators
                    //comparisons.Add(getComp(dpswarchar, "Berserker Rage", (int)Maintenance.BerserkerRage));
                    //comparisons.Add(getComp(dpswarchar, "Deadly Calm", (int)Maintenance.DeadlyCalm));
                    #endregion
                    #region Maintenance
                    //comparisons.Add(getComp(dpswarchar, "Battle Shout", (int)Maintenance.BattleShout));
                    //comparisons.Add(getComp(dpswarchar, "Commanding Shout", (int)Maintenance.CommandingShout));
                    //comparisons.Add(getComp(dpswarchar, "Demoralizing Shout", (int)Maintenance.DemoralizingShout));
                    //comparisons.Add(getComp(dpswarchar, "Sunder Armor", (int)Maintenance.SunderArmor));
                    //comparisons.Add(getComp(dpswarchar, "Thunder Clap", (int)Maintenance.ThunderClap));
                    //comparisons.Add(getComp(dpswarchar, "Hamstring", (int)Maintenance.Hamstring));
                    #endregion
                    #region Periodics
                    //comparisons.Add(getComp(dpswarchar, "Shattering Throw", (int)Maintenance.ShatteringThrow));
                    //comparisons.Add(getComp(dpswarchar, "Sweeping Strikes", (int)Maintenance.SweepingStrikes));
                    //comparisons.Add(getComp(dpswarchar, "Death Wish", (int)Maintenance.DeathWish));
                    //comparisons.Add(getComp(dpswarchar, "Recklessness", (int)Maintenance.Recklessness));
                    //comparisons.Add(getComp(dpswarchar, "Enraged Regeneration", (int)Maintenance.EnragedRegeneration));
                    #endregion
                    #region Damage Dealers
                    if (calculations.Rot.GetType() == typeof(FuryRotation)) {
                        #region Fury
                        //comparisons.Add(getComp(dpswarchar, "Bloodsurge", (int)Maintenance.Bloodsurge));
                        //comparisons.Add(getComp(dpswarchar, "Bloodthirst", (int)Maintenance.Bloodthirst));
                        //comparisons.Add(getComp(dpswarchar, "Whirlwind", (int)Maintenance.Whirlwind));
                        //comparisons.Add(getComp(dpswarchar, "Raging Blow", (int)Maintenance.RagingBlow));
                        #endregion
                    } else if (calculations.Rot.GetType() == typeof(ArmsRotation)) {
                        #region Arms
                        //comparisons.Add(getComp(dpswarchar, "Bladestorm", (int)Maintenance.Bladestorm));
                        //comparisons.Add(getComp(dpswarchar, "Mortal Strike", (int)Maintenance.MortalStrike));
                        //comparisons.Add(getComp(dpswarchar, "Rend", (int)Maintenance.Rend));
                        //comparisons.Add(getComp(dpswarchar, "Overpower", (int)Maintenance.Overpower));
                        //comparisons.Add(getComp(dpswarchar, "Taste for Blood", (int)Maintenance.TasteForBlood));
                        //comparisons.Add(getComp(dpswarchar, "Colossus Smash", (int)Maintenance.ColossusSmash));
                        //comparisons.Add(getComp(dpswarchar, "Slam", (int)Maintenance.Slam));
                        #endregion
                    }
                    //comparisons.Add(getComp(dpswarchar, "<20% Execute Spamming", (int)Maintenance.ExecuteSpam));
                    #endregion
                    #region Rage Dumps
                    //comparisons.Add(getComp(dpswarchar, "Heroic Strike", (int)Maintenance.HeroicStrike));
                    //comparisons.Add(getComp(dpswarchar, "Cleave", (int)Maintenance.Cleave));
                    //comparisons.Add(getComp(dpswarchar, "Inner Rage", (int)Maintenance.InnerRage));
                    #endregion
                    foreach (ComparisonCalculationMarauder comp in comparisons) {
                        comp.OverallPoints = comp.DPSPoints + comp.SurvPoints;
                    }
                    calcOpts.MaintenanceTree = origMaints;
                    ((CalculationOptionsPanelMarauder)CalculationOptionsPanel).IsLoadingCalculationOptions = false;
                    return comparisons.ToArray();
                }
                #endregion
                #region Rage Cost per Damage
                case "Rage Cost per Damage": {
                    List<ComparisonCalculationBase> comparisons = new List<ComparisonCalculationBase>();
                    foreach (AbilityWrapper aw in calculations.Rot.TheAbilityList)
                    {
                        if (aw.Ability.DamageOnUse == 0 || aw.Ability.ResourceCost == -1f) { continue; }
                        ComparisonCalculationMarauder comparison = new ComparisonCalculationMarauder();
                        comparison.Name = aw.Ability.Name;
                        comparison.Description = string.Format("Costs {0} Rage\r\n{1}", aw.Ability.ResourceCost, aw.Ability.Desc);
                        float val = (((aw.Ability.ResourceCost) != 0) ? (aw.Ability.ResourceCost < -1f ? aw.Ability.ResourceCost * -1f : aw.Ability.ResourceCost) : 1f);
                        comparison.SubPoints[0] = aw.Ability.DamageOnUse / val;
                        comparison.SubPoints[1] = (aw.Ability.MHAtkTable.Crit) / val;
                        comparison.ImageSource = aw.Ability.Icon;
                        comparisons.Add(comparison);
                    }
                    foreach (ComparisonCalculationMarauder comp in comparisons) {
                        comp.OverallPoints = comp.SubPoints[0] + comp.SubPoints[1];
                    }
                    calcOpts.MaintenanceTree = origMaints;
                    ((CalculationOptionsPanelMarauder)CalculationOptionsPanel).IsLoadingCalculationOptions = false;
                    return comparisons.ToArray();
                }
                #endregion
                #region Execute Spam
                case "Execute Spam": {
                    List<ComparisonCalculationBase> comparisons = new List<ComparisonCalculationBase>();
                    {
                        //bool orig = ((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpam;
                        //bool orig2 = ((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpamStage2;
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpam = false;
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpamStage2 = false;
                        //CharacterCalculationsMarauder bah = GetCharacterCalculations(zeClone) as CharacterCalculationsMarauder;
                        //ComparisonCalculationMarauder comparison = new ComparisonCalculationMarauder();
                        //comparison.Name = "Without Execute Spam";
                        //comparison.Description = "Turning <20% Execute Spam off on the options pane will change your DPS to this";
                        //comparison.SubPoints[0] = GetCharacterCalculations(zeClone).SubPoints[0];
                        //comparison.SubPoints[1] = GetCharacterCalculations(zeClone).SubPoints[1];
                        //comparison.Equipped = orig == false;
                        //comparison.ImageSource = MortalStrike.SIcon;
                        //comparisons.Add(comparison);
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpam = orig;
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpamStage2 = orig2;
                    }
                    {
                        //bool orig = ((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpam;
                        //bool orig2 = ((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpamStage2;
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpam = true;
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpamStage2 = false;
                        //CharacterCalculationsMarauder bah = GetCharacterCalculations(zeClone) as CharacterCalculationsMarauder;
                        //ComparisonCalculationMarauder comparison = new ComparisonCalculationMarauder();
                        //comparison.Name = "With Execute Spam";
                        //comparison.Description = "Turning <20% Execute Spam on on the options pane will change your DPS to this";
                        //comparison.SubPoints[0] = GetCharacterCalculations(zeClone).SubPoints[0];
                        //comparison.SubPoints[1] = GetCharacterCalculations(zeClone).SubPoints[1];
                        //comparison.Equipped = orig == true && orig2 == false;
                        //comparison.ImageSource = Execute.SIcon;
                        //comparisons.Add(comparison);
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpam = orig;
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpamStage2 = orig2;
                    }
                    {
                        //bool orig = ((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpam;
                        //bool orig2 = ((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpamStage2;
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpam = true;
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpamStage2 = true;
                        //CharacterCalculationsMarauder bah = GetCharacterCalculations(zeClone) as CharacterCalculationsMarauder;
                        //ComparisonCalculationMarauder comparison = new ComparisonCalculationMarauder();
                        //comparison.Name = "With Execute Spam Stage 2";
                        //comparison.Description = "Turning <20% Execute Spam on on the options pane AND Enforcing Taste for Blood will change your DPS to this";
                        //comparison.SubPoints[0] = GetCharacterCalculations(zeClone).SubPoints[0];
                        //comparison.SubPoints[1] = GetCharacterCalculations(zeClone).SubPoints[1];
                        //comparison.Equipped = orig == true && orig2 == true;
                        //comparison.ImageSource = TasteForBlood.SIcon;
                        //comparisons.Add(comparison);
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpam = orig;
                        //((CalculationOptionsMarauder)zeClone.CalculationOptions).M_ExecuteSpamStage2 = orig2;
                    }
                    foreach (ComparisonCalculationMarauder comp in comparisons)
                    {
                        comp.OverallPoints = comp.SubPoints[0] + comp.SubPoints[1];
                    }
                    calcOpts.MaintenanceTree = origMaints;
                    ((CalculationOptionsPanelMarauder)CalculationOptionsPanel).IsLoadingCalculationOptions = false;
                    return comparisons.ToArray();
                }
                #endregion
                #region PTR Testing
                case "PTR Testing":
                    {
                        List<ComparisonCalculationBase> comparisons = new List<ComparisonCalculationBase>();
                        {
                            bool orig = ((CalculationOptionsMarauder)zeClone.CalculationOptions).PtrMode;
                            ((CalculationOptionsMarauder)zeClone.CalculationOptions).PtrMode = false;
                            CharacterCalculationsMarauder bah = GetCharacterCalculations(zeClone) as CharacterCalculationsMarauder;
                            ComparisonCalculationMarauder comparison = new ComparisonCalculationMarauder();
                            comparison.Name = "Live Mode";
                            comparison.Description = "This makes Thunderclap apply Rend's Initial Damage along with its own.";
                            comparison.SubPoints[0] = GetCharacterCalculations(zeClone).SubPoints[0];
                            comparison.SubPoints[1] = GetCharacterCalculations(zeClone).SubPoints[1];
                            comparison.Equipped = orig == false;
                            comparison.ImageSource = "spell_nature_callstorm";
                            comparisons.Add(comparison);
                            ((CalculationOptionsMarauder)zeClone.CalculationOptions).PtrMode = orig;
                        }
                        {
                            bool orig = ((CalculationOptionsMarauder)zeClone.CalculationOptions).PtrMode;
                            ((CalculationOptionsMarauder)zeClone.CalculationOptions).PtrMode = true;
                            CharacterCalculationsMarauder bah = GetCharacterCalculations(zeClone) as CharacterCalculationsMarauder;
                            ComparisonCalculationMarauder comparison = new ComparisonCalculationMarauder();
                            comparison.Name = "PTR Mode";
                            comparison.Description = "This makes Thunderclap NOT apply Rend's Initial Damage along with its own.";
                            comparison.SubPoints[0] = GetCharacterCalculations(zeClone).SubPoints[0];
                            comparison.SubPoints[1] = GetCharacterCalculations(zeClone).SubPoints[1];
                            comparison.Equipped = orig == true;
                            //comparison.ImageSource = Rend.SIcon;
                            comparisons.Add(comparison);
                            ((CalculationOptionsMarauder)zeClone.CalculationOptions).PtrMode = orig;
                        }
                        foreach (ComparisonCalculationMarauder comp in comparisons)
                        {
                            comp.OverallPoints = comp.SubPoints[0] + comp.SubPoints[1];
                        }
                        calcOpts.MaintenanceTree = origMaints;
                        ((CalculationOptionsPanelMarauder)CalculationOptionsPanel).IsLoadingCalculationOptions = false;
                        return comparisons.ToArray();
                    }
                #endregion
                default: { calcOpts.MaintenanceTree = origMaints; return new ComparisonCalculationBase[0]; }
            }
        }
        #endregion

        #region Character Calcs

        public override CharacterCalculationsBase GetCharacterCalculations(Character character, Item additionalItem, bool referenceCalculation, bool significantChange, bool needsDisplayCalculations)
        {
            CharacterCalculationsMarauder calc = new CharacterCalculationsMarauder();
            try {
                #region Setup uniform variables from all models
                // First things first, we need to ensure that we aren't using bad data
                if (character == null) { return calc; }
                CalculationOptionsMarauder calcOpts = character.CalculationOptions as CalculationOptionsMarauder;
                if (calcOpts == null) { return calc; }
                //
                BossOptions bossOpts = character.BossOptions;
                // Make sure there is at least one attack in the list.
                // If there's not, add a Default Melee Attack for processing
                if (bossOpts.Attacks.Count < 1) {
                    character.IsLoading = true;
                    bossOpts.DamagingTargs = true;
                    bossOpts.Attacks.Add(BossHandler.ADefaultMeleeAttack);
                    character.IsLoading = false;
                }
                // Make sure there is a default melee attack
                // If the above processed, there will be one so this won't have to process
                // If the above didn't process and there isn't one, add one now
                if (bossOpts.DefaultMeleeAttack == null) {
                    character.IsLoading = true;
                    bossOpts.DamagingTargs = true;
                    bossOpts.Attacks.Add(BossHandler.ADefaultMeleeAttack);
                    character.IsLoading = false;
                }
                // Since the above forced there to be an attack it's safe to do this without a null check
                CombatFactorsMarauder combatFactors;
                RotationMarauder Rot;
                StatsWarrior statsRace = null;
                StatsWarrior stats = GetCharacterStats(character, additionalItem, StatType.Average, calcOpts, bossOpts,
                    out statsRace, out combatFactors, out Rot);
                calc.BasicStats = stats;
                calc.TargetLevel = bossOpts.Level;

                int levelDifference = (bossOpts.Level - character.Level);

                CollectedCharacterMarauder collChar = new CollectedCharacterMarauder() {
                    CalcOpts = calcOpts,
                    BossOpts = bossOpts,
                    Char = character,
                    StatS = stats,
                    Talents = character.MarauderTalents,
                    CombatFactors = combatFactors,
                    Rotation = Rot,
                };
                #endregion

                calc.Duration = bossOpts.BerserkTimer;
                calc.PTRMode = calcOpts.PtrMode;

                calc.AverageStats = stats;
                if (needsDisplayCalculations) {
                    calc.UnbuffedStats = GetCharacterStats(character, additionalItem, StatType.Unbuffed, calcOpts, bossOpts, out statsRace);
                    calc.BuffedStats = GetCharacterStats(character, additionalItem, StatType.Buffed, calcOpts, bossOpts, out statsRace);
                    calc.BuffsStats = GetBuffsStats(collChar);
                    calc.MaximumStats = GetCharacterStats(character, additionalItem, StatType.Maximum, calcOpts, bossOpts, out statsRace);
                }

                calc.CombatFactors = combatFactors;
                calc.Rot = Rot;
                calc.BaseHealth = statsRace.Health; 
                {// == Attack Table ==
                    // Miss
                    calc.Miss = stats.Miss;
                    calc.AccuracyRating = stats.AccuracyRating;
                    calc.CritRating = stats.CritRating;
                    //calc.CritPercent = stats.PhysicalCrit;
                    calc.MHCrit = combatFactors.C_PlayersMhChanceToCritTarget_Basic;
                    calc.OHCrit = combatFactors.C_PlayersOhChanceToCritTarget_Basic;
                }
                // Offensive
                calc.AlacrityRating = stats.AlacrityRating;
                calc.AlacrityPercent = stats.Alacrity;
                
                // DPS
                Rot.Initialize(calc);

                // Neutral
                // Defensive
                calc.Armor = stats.Armor; 

                Rot.MakeRotationandDoDPS(needsDisplayCalculations);

                #region Special Damage Procs, like Bandit's Insignia or Hand-mounted Pyro Rockets
                calc.SpecProcDPS = calc.SpecProcDmgPerHit = calc.SpecProcActs = 0f;
                if (stats._rawSpecialEffectData != null && character.MainHand != null) {
                    bool runIt = false;
                    foreach (SpecialEffect s in stats.SpecialEffects()) {
                        if (s.Stats == null) { continue; }
                        if (s.Stats.KineticDamage != 0) { runIt = true; break; }
                        if (s.Stats.EnergyDamage   != 0) { runIt = true; break; }
                        if (s.Stats.InternalDamage   != 0) { runIt = true; break; }
                        if (s.Stats.ElementalDamage  != 0) { runIt = true; break; }
                    }
                    if (runIt) {
                        DamageProcs.SpecialDamageProcs SDP;
                        Dictionary<Trigger, float> triggerIntervals = new Dictionary<Trigger, float>();
                        Dictionary<Trigger, float> triggerChances = new Dictionary<Trigger, float>();
                        CalculateTriggers(collChar, triggerIntervals, triggerChances);
                        SDP = new Rage.DamageProcs.SpecialDamageProcs(character, stats, calc.TargetLevel - character.Level,
                            new List<SpecialEffect>(stats.SpecialEffects()),
                            triggerIntervals, triggerChances,
                            bossOpts.BerserkTimer,
                            combatFactors.TargetsDamageReductionMultiplier);

                        calc.SpecProcDPS = SDP.CalculateAll();
                        calc.SpecProcDmgPerHit = SDP.GetDamagePerHit;
                        calc.SpecProcActs = SDP.GetTotalNumProcs;
                    }
                }
                calc.TotalDPS += calc.SpecProcDPS;
                #endregion

                #region Survivability
                List<Attack> Attacks;
                if (calcOpts.SurvScale != 0f && (Attacks = bossOpts.Attacks.FindAll(a => a.AffectsRole[PLAYER_ROLES.MeleeDPS])).Count > 0) {
                    Dictionary<ItemDamageType, float> countsDmg = new Dictionary<ItemDamageType, float>() {
                        { ItemDamageType.Kinetic, 0f },
                        { ItemDamageType.Energy, 0f },
                        { ItemDamageType.Internal, 0f },
                        { ItemDamageType.Elemental, 0f },
                    };
                    Dictionary<ItemDamageType, float> percDmg = new Dictionary<ItemDamageType, float>() {
                        { ItemDamageType.Kinetic, 0f },
                        { ItemDamageType.Energy, 0f },
                        { ItemDamageType.Internal, 0f },
                        { ItemDamageType.Elemental, 0f },
                    };
                    Dictionary<ItemDamageType, float> highestDmg = new Dictionary<ItemDamageType, float>() {
                        { ItemDamageType.Kinetic, 0f },
                        { ItemDamageType.Energy, 0f },
                        { ItemDamageType.Internal, 0f },
                        { ItemDamageType.Elemental, 0f },
                    };
                    int totalCount = 0;
                    foreach (Attack a in Attacks) {
                        countsDmg[a.DamageType] += 1; totalCount++;
                        if ((a.DamagePerHit + a.DamagePerTick) > highestDmg[a.DamageType]) {
                            highestDmg[a.DamageType] = (a.DamagePerHit + a.DamagePerTick);
                        }
                    }
                    foreach (ItemDamageType t in countsDmg.Keys) { percDmg[t] = countsDmg[t] / (float)totalCount; }
                    float TotalConstantDamageReduction = 1f - (1f - (stats.Armor > 0 ? StatConversion.GetArmorDamageReduction(stats.Armor, 0f, 0f) : 0))
                                                            * (1f - stats.DamageTakenReductionMultiplier)
                                                            * (1f - stats.BossPhysicalDamageDealtReductionMultiplier);
                    Dictionary<ItemDamageType, float> SurvivabilityPointsRaw = new Dictionary<ItemDamageType, float>() {
                        { ItemDamageType.Kinetic,   stats.Health / (1f - TotalConstantDamageReduction) },
                        { ItemDamageType.Energy,    stats.Health / ((1f - StatConversion.GetMinimumResistance(bossOpts.Level, character.Level, stats.EnergyResistance,     0)) * (1f - stats.DamageTakenReductionMultiplier)) },
                        { ItemDamageType.Internal,  stats.Health / ((1f - StatConversion.GetMinimumResistance(bossOpts.Level, character.Level, stats.InternalResistance,   0)) * (1f - stats.DamageTakenReductionMultiplier)) },
                        { ItemDamageType.Elemental, stats.Health / ((1f - StatConversion.GetMinimumResistance(bossOpts.Level, character.Level, stats.ElementalResistance,  0)) * (1f - stats.DamageTakenReductionMultiplier)) },
                    };
                    Dictionary<ItemDamageType, float> SurvivabilityPoints = SoftCapSurvivals(collChar, highestDmg, SurvivabilityPointsRaw);

                    if (stats.HealthRestoreFromMaxHealth > 0) {
                        stats.HealthRestore += stats.HealthRestoreFromMaxHealth * stats.Health * bossOpts.BerserkTimer;
                    }

                    float survs = 0f;
                    foreach (ItemDamageType t in SurvivabilityPoints.Keys) { survs += SurvivabilityPoints[t] * percDmg[t]; }
                    float HealthRest2Surv = survs / 100f + Rot._HPS_TTL_O20 + stats.HealthRestore / 100f;
                    calc.TotalHPS = Rot._HPS_TTL_O20;
                    calc.Survivability = calcOpts.SurvScale * HealthRest2Surv;
                } else {
                    // No point in running all those calcs just to zero them out after
                    calc.TotalHPS = Rot._HPS_TTL_O20;
                    calc.Survivability = 0f;
                }
                #endregion

                calc.OverallPoints = calc.TotalDPS + calc.Survivability;
            } catch (Exception ex) {
                new Base.ErrorBox() {
                    Title = "Error in creating Stat Pane Calculations",
                    Function = "GetCharacterCalculations()",
                    TheException = ex,
                }.Show();
            }
            return calc;
        }

        static double fourToTheNegativeFourThirds = Math.Pow(4d, -4d / 3d);
        static double topRight = Math.Pow(fourToTheNegativeFourThirds, 1d / 4d);
        public float SoftCapSurv(float raw, float damage) {
            if (damage <= 0f) { return 0f; }
            double survivalCap = damage * 2.0f / 1000d;
            double survivalRaw = raw / 1000d;

            //Implement Survival Soft Cap
            if (survivalRaw <= survivalCap) {
                return 1000f * (float)survivalRaw;
            } else {
                double x = survivalRaw;
                double cap = survivalCap;
                double topLeft = Math.Pow(((x - cap) / cap) + fourToTheNegativeFourThirds, 1d / 4d);
                double fracTop = topLeft - topRight;
                double fraction = fracTop / 2d;
                double y = (cap * fraction + cap);
                return 1000f * (float)y;
            }
        }

        public Dictionary<ItemDamageType, float> SoftCapSurvivals(CollectedCharacterMarauder collChar, Dictionary<ItemDamageType, float> highestDmg, Dictionary<ItemDamageType, float> survivalRaws) {
            Dictionary<ItemDamageType, float> cappedValues = new Dictionary<ItemDamageType, float>() { };
            //
            foreach (ItemDamageType t in highestDmg.Keys) {
                cappedValues[t] = SoftCapSurv(survivalRaws[t], highestDmg[t]);
            }
            //
            return cappedValues;
        }

        public override Stats GetCharacterStats(Character character, Item additionalItem) {
            if (character == null) { return new Stats(); }
            try {
                Base.StatsWarrior statsRace = null;
                return GetCharacterStats(character, additionalItem, StatType.Average, (CalculationOptionsMarauder)character.CalculationOptions, character.BossOptions, out statsRace);
            } catch (Exception ex) {
                new Base.ErrorBox()
                {
                    Title = "Error in getting Character Stats",
                    Function = "GetCharacterStats()",
                    TheException = ex,
                }.Show();
            }
            return new Stats() { };
        }

        private Base.StatsWarrior GetCharacterStats_Buffed(CollectedCharacterMarauder dpswarchar, Item additionalItem, bool isBuffed, out Base.StatsWarrior statsRace)
        {
#if DEBUG
            //ConstructionCounts["GetCharacterStats_Buffed"]++;
#endif
            if (dpswarchar.CalcOpts == null) { dpswarchar.CalcOpts = dpswarchar.Char.CalculationOptions as CalculationOptionsMarauder; }
            if (dpswarchar.BossOpts == null) { dpswarchar.BossOpts = dpswarchar.Char.BossOptions; }
            if (dpswarchar.CombatFactors == null) { dpswarchar.CombatFactors = new CombatFactors(dpswarchar.Char, new Base.StatsWarrior(), dpswarchar.CalcOpts, dpswarchar.BossOpts); }
            MarauderTalents talents = dpswarchar.Char.MarauderTalents;

            #region From Race
            statsRace = new Base.StatsWarrior();
            statsRace.Accumulate(BaseStats.GetBaseStats(dpswarchar.Char.Level, CharacterClass.Marauder, dpswarchar.Char.Race));
            #endregion
            #region From Gear/Buffs
            Base.StatsWarrior statsBuffs = (isBuffed ? GetBuffsStats(dpswarchar) : new Base.StatsWarrior());
            Base.StatsWarrior statsItems = new Base.StatsWarrior();
            statsItems.Accumulate(GetItemStats(dpswarchar.Char, additionalItem));
            #endregion
            #region From Options
            Base.StatsWarrior statsOptionsPanel = new Base.StatsWarrior()
            {
                //BonusStrengthMultiplier = (dpswarchar.combatFactors.FuryStance ? talents.Annihilate * 0.04f : 0f),
                //PhysicalCrit = (dpswarchar.combatFactors.FuryStance ? 0.03f + statsBuffs.BonusWarrior_T9_2P_Crit : 0f),
                // Stance Related Damage Given/Taken mods
                /*
                  Battle Stance
                        A balanced combat stance.  Increases damage done by 5%.  Decreases damage taken by 5%.
                  Berserker Stance
                        An aggressive combat stance.  Increases damage done by 10%.
                */
                DamageTakenReductionMultiplier = 0f,
                BonusDamageMultiplier = 0.10f,

                // Battle Shout
                //Strength = (dpswarchar.CalcOpts.M_BattleShout ? 549f : 0f),
                //Cunning = (dpswarchar.CalcOpts.M_BattleShout ? 549f : 0f),
                // Commanding Shout
                //Endurance = (dpswarchar.CalcOpts.M_CommandingShout ? 585f : 0f),
                // Demo Shout
                //BossPhysicalDamageDealtReductionMultiplier = (dpswarchar.CalcOpts.M_DemoralizingShout ? 0.10f : 0f),
                // Sunder Armor
                //ArmorPenetration = (dpswarchar.CalcOpts.M_SunderArmor ? 0.04f * 3f : 0f),
                // Thunder Clap
                //BossAttackSpeedReductionMultiplier = (dpswarchar.CalcOpts.M_ThunderClap ? 0.20f : 0f),
            };
            //if (dpswarchar.CalcOpts.M_ColossusSmash) { statsOptionsPanel.AddSpecialEffect(TalentsAsSpecialEffects.ColossusSmash); }
            #endregion
            #region From Talents
            Base.StatsWarrior statsTalents = new Base.StatsWarrior()
            {
                // Offensive
                /*BonusPhysicalDamageMultiplier = ((dpswarchar.CalcOpts.M_Rend // Have Rend up
                                                 || talents.Annihilate > 0 // Have Deep Wounds
                                                ? (1.0f + talents.Annihilate * 0.02f) : 1.0f)
                                                * (dpswarchar.CombatFactors.FuryStance
                                                ? 1.05f : 1.0f))
                                                - 1f,*/
                /*BonusBleedDamageMultiplier = (dpswarchar.CalcOpts.M_Rend // Have Rend up
                                                 || talents.Annihilate > 0 // Have Deep Wounds
                                                ? talents.Annihilate * 0.15f : 0f),*/
                //PhysicalCrit = (talents.Annihilate > 0 && isBuffed ? 0.05f + 0.02f : 0f), // Cata has a new +2% on self (group gets 5%, self gets total 7%)
                // Defensive
                BonusArmorMultiplier = talents.Annihilate * 0.10f / 3f,
                BonusHealingReceived = talents.Annihilate * 0.03f,
            };
            // Add Talents that give SpecialEffects
            if (talents.Annihilate > 0 && dpswarchar.Char.MainHand != null) { statsTalents.AddSpecialEffect(TalentsAsSpecialEffects.WreckingCrew[talents.Annihilate]); }
            if (talents.Annihilate > 0) { statsTalents.AddSpecialEffect(TalentsAsSpecialEffects.BloodCraze[talents.Annihilate]); }
            //if (talents.Annihilate > 0 && dpswarchar.CalcOpts.M_ExecuteSpam) { statsTalents.AddSpecialEffect(TalentsAsSpecialEffects.Executioner[talents.Annihilate]); }
            //if (talents.Annihilate > 0) { statsTalents.AddSpecialEffect(TalentsAsSpecialEffects.BloodFrenzy[talents.Annihilate]); }
            #endregion

            Base.StatsWarrior statsTotal = new Base.StatsWarrior();
            statsTotal.Accumulate(statsRace);
            statsTotal.Accumulate(statsItems);
            statsTotal.Accumulate(statsBuffs);
            statsTotal.Accumulate(statsTalents);
            statsTotal.Accumulate(statsOptionsPanel);
            statsTotal = UpdateStatsAndAdd(statsTotal, null, dpswarchar.Char);
            //Stats statsProcs = new Stats();

            // Dodge (your dodging incoming attacks)
            // Warriors no longer gain dodge from Agility since patch 4.2
            // statsTotal.Dodge += StatConversion.GetDodgeFromAgility(statsTotal.Agility, dpswarchar.Char.Class);
            statsTotal.Dodge += StatConversion.GetDefenseFromRating(statsTotal.DefenseRating) / 2f;

            // Parry (your parrying incoming attacks)
            statsTotal.Parry += StatConversion.GetDefenseFromRating(statsTotal.DefenseRating) / 2f;

            return statsTotal;
        }

        private Base.StatsWarrior GetCharacterStats(Character character, Item additionalItem, StatType statType, CalculationOptionsMarauder calcOpts, BossOptions bossOpts,
            out Base.StatsWarrior statsRace)
        {
            CombatFactorsMarauder temp; RotationMarauder temp3;
            return GetCharacterStats(character, additionalItem, statType, calcOpts, bossOpts, out statsRace, out temp, out temp3);
        }

        private Base.StatsWarrior GetCharacterStats(Character character, Item additionalItem, StatType statType, CalculationOptionsMarauder calcOpts, BossOptions bossOpts,
            out Base.StatsWarrior statsRace, out CombatFactorsMarauder combatFactors, out RotationMarauder Rot)
        {
            CollectedCharacterMarauder CollChar = new CollectedCharacterMarauder { Char = character, CalcOpts = calcOpts, BossOpts = bossOpts, Talents = character.MarauderTalents, CombatFactors = null, Rotation = null };
            Base.StatsWarrior statsTotal = GetCharacterStats_Buffed(CollChar, additionalItem, statType != StatType.Unbuffed, out statsRace);
            CollChar.StatS = statsTotal;
            combatFactors = new CombatFactorsMarauder(character, statsTotal, calcOpts, bossOpts); // we have to regenerate it here
            CollChar.CombatFactors = combatFactors;
            Rot = new ArmsRotation(CollChar);
            CollChar.Rotation = Rot;
            
            if (statType == (StatType.Buffed | StatType.Unbuffed)) { return statsTotal; }
            /*Rot.SetRotationAbilities(CollChar.CalcOpts.GetActiveRotationOpener().ClassAbilityPriority,
                CollChar.CalcOpts.GetActiveRotation().ClassAbilityPriority,
                CollChar.CalcOpts.GetActiveRotation().FillerAsType,
                CollChar.CalcOpts.GetActiveRotationExecute().ClassAbilityPriority,
                CollChar.CalcOpts.GetActiveRotationExecute().FillerAsType);*/
            // SpecialEffects: Supposed to handle all procs such as Berserking, Mirror of Truth, Grim Toll, etc.
            Rot.Initialize();
            Rot.MakeRotationandDoDPS(false);
            Rot.AddValidatedSpecialEffects(statsTotal, character.MarauderTalents);

            CollectedCharacterMarauder collChar = new CollectedCharacterMarauder(){
                CalcOpts = calcOpts,
                BossOpts = bossOpts,
                Char = character,
                CombatFactors = combatFactors,
                Rotation = Rot,
                Talents = character.MarauderTalents,
                StatS = statsTotal,
            };

            float fightDuration = bossOpts.BerserkTimer;

            if (statType == StatType.Average) {
                DoSpecialEffects(collChar, statsTotal);
            }
            else // if (statType == StatType.Maximum)
            {
                Base.StatsWarrior maxSpecEffects = new Base.StatsWarrior();
                foreach (SpecialEffect effect in statsTotal.SpecialEffects()) maxSpecEffects.Accumulate(effect.Stats);
                return UpdateStatsAndAdd(maxSpecEffects as Base.StatsWarrior, combatFactors.StatS as Base.StatsWarrior, character);
            }
            //UpdateStatsAndAdd(statsProcs, statsTotal, character); // Already done in GetSpecialEffectStats

            // special case for dual wielding w/ berserker enchant on one/both weapons, as they act independently
            //combatFactors.StatS = statsTotal;
            combatFactors.InvalidateCache();
            return combatFactors.StatS as Base.StatsWarrior;
        }

        private void DoSpecialEffects(CollectedCharacterMarauder charStruct, Base.StatsWarrior statsTotal)
        {
            #region Initialize Triggers
            Dictionary<Trigger, float> triggerIntervals = new Dictionary<Trigger, float>();
            Dictionary<Trigger, float> triggerChances = new Dictionary<Trigger, float>();

            CalculateTriggers(charStruct, triggerIntervals, triggerChances);
            #endregion

            List<SpecialEffect> critEffects = new List<SpecialEffect>();

            List<SpecialEffect> firstPass = new List<SpecialEffect>();
            List<SpecialEffect> secondPass = new List<SpecialEffect>();
            List<SpecialEffect> thirdPass = new List<SpecialEffect>();
            foreach (SpecialEffect effect in statsTotal.SpecialEffects())
            {
                effect.Stats.GenerateSparseData();

                if (!triggerIntervals.ContainsKey(effect.Trigger)) continue;
                else if (effect.Stats.CritRating > 0f) { critEffects.Add(effect); }
                else if (effect.Stats.AlacrityRating > 0f ||
                    effect.Stats.AccuracyRating > 0f ||
                    effect.Stats.CritRating > 0f ||
                    effect.Stats.Alacrity > 0f //||
                    //effect.Stats.PhysicalCrit > 0f ||
                    //effect.Stats.PhysicalHit > 0f
                    )
                {
                    // These procs affect rotation
                    firstPass.Add(effect);
                }
                else if (effect.Stats.KineticDamage > 0 ||
                    effect.Stats.EnergyDamage > 0f ||
                    effect.Stats.InternalDamage > 0f ||
                    effect.Stats.ElementalDamage > 0f)
                {
                    // It's a Special Damage Proc
                    thirdPass.Add(effect);
                }
                else
                {
                    // Any other stat proc
                    secondPass.Add(effect);
                }
            }

            IterativeSpecialEffectsStats(charStruct, firstPass, critEffects, triggerIntervals, triggerChances, true, new Base.StatsWarrior(), charStruct.CombatFactors.StatS as Base.StatsWarrior);
            IterativeSpecialEffectsStats(charStruct, secondPass, critEffects, triggerIntervals, triggerChances, false, null, charStruct.CombatFactors.StatS as Base.StatsWarrior);
            IterativeSpecialEffectsStats(charStruct, thirdPass, critEffects, triggerIntervals, triggerChances, false, null, charStruct.CombatFactors.StatS as Base.StatsWarrior);
        }

        private static void CalculateTriggers(CollectedCharacterMarauder charStruct, Dictionary<Trigger, float> triggerIntervals, Dictionary<Trigger, float> triggerChances)
        {
#if DEBUG
            string addInfo = "No Additional Info";
#endif
            try
            {
                float fightDuration = charStruct.BossOpts.BerserkTimer;
                float fightDurationO20 = charStruct.Rotation.FightDurationO20;
                float fightDurationU20 = charStruct.Rotation.FightDurationU20;
#if DEBUG
                addInfo = "FightDur Passed";
#endif
                //float fightDuration2Pass = charStruct.calcOpts.SE_UseDur ? fightDuration : 0;

                float attemptedMH = charStruct.Rotation.AttemptedAtksOverDurMH;
                float attemptedOH = charStruct.Rotation.AttemptedAtksOverDurOH;
                float attempted = attemptedMH + attemptedOH;

                float landMH = charStruct.Rotation.LandedAtksOverDurMH;
                float landOH = charStruct.Rotation.LandedAtksOverDurOH;
                float land = landMH + landOH;

                float crit = charStruct.Rotation.CriticalAtksOverDur;

                float avoidedAttacks = charStruct.CombatFactors.StatS.Dodge + charStruct.CombatFactors.StatS.Parry;

#if DEBUG
                addInfo += "\r\nBuncha Floats started";
#endif
                float bleed = fightDuration; /** (charStruct.CombatFactors.FuryStance || !charStruct.CalcOpts.MaintenanceTree[(int)Maintenance.Rend] ? 0f : 1f / 3f*///);

                float bleedHitInterval = fightDuration / bleed;
                float attemptedAtkInterval = fightDuration / attempted;
                float attemptedAtksIntervalMH = fightDuration / attemptedMH;
                float attemptedAtksIntervalOH = fightDuration / attemptedOH;
                //float landedAtksInterval = fightDuration / land;
                float dmgDoneInterval = fightDuration / (land + bleed);
                float dmgTakenInterval = 0f;
                {
                    List<Attack> attacks = charStruct.BossOpts.Attacks.FindAll(a => a.AffectsRole[PLAYER_ROLES.MeleeDPS] && a.Validate);
                    float frequency = 0;
                    foreach (Attack a in attacks) {
                        frequency += 1f / a.AttackSpeed;
                    }
                    dmgTakenInterval = 1f / frequency;
                }
                
#if DEBUG
                addInfo += "\r\nAoETargsFreq Passed";
#endif
                float hitRate = 1, hitRateMH = 1, hitRateOH = 1, critRate = 1;
                if (attempted != 0f)
                {
                    hitRate = land / attempted;
                    critRate = crit / attempted;
                }
                if (attemptedMH != 0f)
                    hitRateMH = landMH / attemptedMH;
                if (attemptedOH != 0f)
                    hitRateOH = landOH / attemptedOH;

#if DEBUG
                addInfo += "\r\nTriggers started";
#endif
                triggerIntervals[Trigger.Use] = 0f;
                triggerChances[Trigger.Use] = 1f;

                triggerIntervals[Trigger.MeleeAttack] = attemptedAtkInterval;
                triggerChances[Trigger.MeleeAttack] = 1f;

                triggerIntervals[Trigger.MeleeHit] = 
                triggerIntervals[Trigger.PhysicalHit] =
                triggerIntervals[Trigger.CurrentHandHit] = 
                triggerIntervals[Trigger.PhysicalAttack] = attemptedAtkInterval;
                triggerChances[Trigger.MeleeHit] = 
                triggerChances[Trigger.CurrentHandHit] = 
                triggerChances[Trigger.PhysicalHit] = hitRate;
                triggerChances[Trigger.PhysicalAttack] = 1f;

                triggerIntervals[Trigger.PhysicalCrit] = triggerIntervals[Trigger.MeleeCrit] = attemptedAtkInterval;
                triggerChances[Trigger.PhysicalCrit] = triggerChances[Trigger.MeleeCrit] = critRate;

                triggerIntervals[Trigger.MainHandHit] = attemptedAtksIntervalMH;
                triggerChances[Trigger.MainHandHit] = hitRateMH;
                triggerIntervals[Trigger.OffHandHit] = attemptedAtksIntervalOH;
                triggerChances[Trigger.OffHandHit] = hitRateOH;

                triggerIntervals[Trigger.DoTTick] = bleedHitInterval;
                triggerChances[Trigger.DoTTick] = 1f;

                triggerIntervals[Trigger.DamageDone] = dmgDoneInterval;
                triggerChances[Trigger.DamageDone] = 1f;

                triggerIntervals[Trigger.DamageOrHealingDone] = dmgDoneInterval; // Need to add Self Heals
                triggerChances[Trigger.DamageOrHealingDone] = 1f;

                triggerIntervals[Trigger.DamageTaken] = dmgTakenInterval;
                triggerChances[Trigger.DamageTaken] = 1f;

                triggerIntervals[Trigger.DamageAvoided] = dmgTakenInterval;
                triggerChances[Trigger.DamageAvoided] = avoidedAttacks;

#if DEBUG
                addInfo += "\r\nFinished";
#endif
            } catch (Exception ex) {
                new Base.ErrorBox() {
                    Title = "Error Calculating Triggers",
                    Function = "CalculateTriggers()",
#if DEBUG
                    Info = addInfo,
#endif
                    TheException = ex,
                }.Show();
            }
        }

        private Stats IterativeSpecialEffectsStats(CollectedCharacterMarauder charStruct, List<SpecialEffect> specialEffects, List<SpecialEffect> critEffects,
            Dictionary<Trigger, float> triggerIntervals, Dictionary<Trigger, float> triggerChances,
            bool iterate, Base.StatsWarrior iterateOld, Base.StatsWarrior originalStats)
        {
            MarauderTalents talents = charStruct.Char.MarauderTalents;
            float fightDuration = charStruct.BossOpts.BerserkTimer;
            Base.StatsWarrior statsProcs = new Base.StatsWarrior();
            try {
                float dmgTakenInterval = fightDuration / charStruct.BossOpts.AoETargsFreq;

                List<Trigger> critTriggers = new List<Trigger>();
                List<float> critWeights = new List<float>();
                bool needsHitTableReset = false;
                foreach (SpecialEffect effect in critEffects) {
                    needsHitTableReset = true;
                    critTriggers.Add(effect.Trigger);
                    critWeights.Add(1f);
                }
                foreach (SpecialEffect effect in specialEffects) {
                    float numProcs = 0;
                    if (effect.Stats.ForceorEquivRestore > 0f && effect.Stats.HealthRestoreFromMaxHealth > 0f) {
                        float value1 = effect.Stats.ForceorEquivRestore;
                        float value2 = effect.Stats.HealthRestoreFromMaxHealth;
                        SpecialEffect dummy = new SpecialEffect(effect.Trigger, new Stats() { ForceorEquivRestore = value1, HealthRestoreFromMaxHealth = value2 }, effect.Duration, effect.Cooldown, effect.Chance) { BypassCache = true };
                        numProcs = dummy.GetAverageProcsPerSecond(dmgTakenInterval, originalStats.Dodge + originalStats.Parry, 0f, 0f) * fightDuration;
                        statsProcs.ForceorEquivRestore += dummy.Stats.ForceorEquivRestore * numProcs;
                        dummy.Stats.ForceorEquivRestore = 0f;
                        ApplySpecialEffect(dummy, charStruct, triggerIntervals, triggerChances, ref statsProcs);
                    } else if (effect.Stats.ForceorEquivRestore > 0f) {
                        numProcs = effect.GetAverageProcsPerSecond(dmgTakenInterval, originalStats.Dodge + originalStats.Parry, 0f, 0f) * fightDuration;
                        statsProcs.ForceorEquivRestore += effect.Stats.ForceorEquivRestore * numProcs;
                    } else if (effect.Stats.HealthRestoreFromMaxHealth > 0f) {
                        numProcs = effect.GetAverageProcsPerSecond(dmgTakenInterval, originalStats.Dodge + originalStats.Parry, 0f, 0f) * fightDuration;
                        statsProcs.HealthRestoreFromMaxHealth += effect.Stats.HealthRestoreFromMaxHealth * numProcs;
                    } else {
                        ApplySpecialEffect(effect, charStruct, triggerIntervals, triggerChances, ref statsProcs);
                    }
                }

                WeightedStat[] critProcs;
                if (critEffects.Count == 0) {
                    critProcs = new WeightedStat[] { new WeightedStat() { Value = 0f, Chance = 1f } };
                } else if (critEffects.Count == 1) {
                    float interval = triggerIntervals[critEffects[0].Trigger];
                    float chance = triggerChances[critEffects[0].Trigger];
                    float upTime = critEffects[0].GetAverageStackSize(interval, chance, 0,
                        (charStruct.CalcOpts.SE_UseDur ? charStruct.BossOpts.BerserkTimer : 0f));
                    upTime *= critWeights[0];
                    critProcs = new WeightedStat[] { new WeightedStat() { Value = critEffects[0].Stats.CritRating, Chance = upTime },
                                                     new WeightedStat() { Value = 0f, Chance = 1f - upTime } };
                } else {
                    float[] intervals = new float[critEffects.Count];
                    float[] chances = new float[critEffects.Count];
                    float[] offset = new float[critEffects.Count];
                    for (int i = 0; i < critEffects.Count; i++) {
                        intervals[i] = triggerIntervals[critEffects[i].Trigger];
                        chances[i] = triggerChances[critEffects[i].Trigger];
                    }

                    critProcs = SpecialEffect.GetAverageCombinedUptimeCombinations(critEffects.ToArray(), intervals, chances, offset, critWeights.ToArray(), 0,
                        charStruct.BossOpts.BerserkTimer, AdditiveStat.CritRating);
                }
                charStruct.CombatFactors.CritProcs = critProcs;
                if (iterate && charStruct.Char.MainHand != null && charStruct.Char.MainHand.Item != null)
                {
                    float mhPerc = 1f; // 100% by default
                    bool useOffHand = false;
                    
                    float temp = 1f / 1.5f;
                    if (charStruct.Char.OffHand != null && charStruct.Char.OffHand.Item != null) {
                        useOffHand = true;
                        temp += 1f / 1.5f;
                        mhPerc = (1.5f / 1.5f) / (1f + 1.5f / 1.5f);
                    }

                    foreach (AbilityWrapper aw in charStruct.Rotation.DamagingAbilities)
                    {
                        if (aw.Ability.CanCrit && aw.AllNumActivates > 0f)
                        {
                            float tempFactor = (float) Math.Pow(1f - aw.Ability.MHAtkTable.Crit,
                                                                (aw.AllNumActivates*aw.Ability.SwingsPerActivate*
                                                                 aw.Ability.AvgTargets/fightDuration));
                            if (aw.Ability.SwingsOffHand && useOffHand) { }
                        }
                    }
                }

                charStruct.CombatFactors.StatS = UpdateStatsAndAdd(statsProcs, originalStats, charStruct.Char);
                charStruct.CombatFactors.InvalidateCache();
                //Rot.InvalidateCache();
                if (iterate) {
                    const float precisionWhole = 0.01f;
                    const float precisionDec = 0.0001f;
                    if (statsProcs.Cunning - iterateOld.Cunning > precisionWhole ||
                        statsProcs.AlacrityRating - iterateOld.AlacrityRating > precisionWhole ||
                        statsProcs.AccuracyRating - iterateOld.AccuracyRating > precisionWhole ||
                        statsProcs.CritRating - iterateOld.CritRating > precisionWhole ||
                        statsProcs.Alacrity - iterateOld.Alacrity > precisionDec //||
                        //statsProcs.PhysicalCrit - iterateOld.PhysicalCrit > precisionDec ||
                        //statsProcs.PhysicalHit - iterateOld.PhysicalHit > precisionDec
                        )
                    {
                        if (needsHitTableReset) charStruct.Rotation.ResetHitTables();
                        charStruct.Rotation.DoIterations();
                        CalculateTriggers(charStruct, triggerIntervals, triggerChances);
                        return IterativeSpecialEffectsStats(charStruct,
                            specialEffects, critEffects, triggerIntervals, triggerChances, true, statsProcs, originalStats);
                    } else { /*int j = 0;*/ }
                }

                return statsProcs;
            } catch (Exception ex) {
                new Base.ErrorBox()
                {
                    Title = "Error in creating SpecialEffects Stats",
                    Function = "GetSpecialEffectsStats()",
                    TheException = ex,
                }.Show();
                return new Stats();
            }
        }

        private float ApplySpecialEffect(SpecialEffect effect, CollectedCharacterMarauder charStruct, Dictionary<Trigger, float> triggerIntervals, Dictionary<Trigger, float> triggerChances, ref Base.StatsWarrior applyTo)
        {
#if DEBUG
            //ConstructionCounts["ApplySpecialEffect"]++;
#endif
            float fightDuration = charStruct.BossOpts.BerserkTimer;
            float fightDuration2Pass = 0;//charStruct.CalcOpts.SE_UseDur ? fightDuration : 0;

            Stats effectStats = effect.Stats;

            float upTime = 0f;
            //float avgStack = 1f;
            /*if (effect.Stats.TargetArmorReduction > 0f || effect.Stats.ArmorPenetrationRating > 0f) {
                //int j = 0;
            }*/
            if (effect.Trigger == Trigger.Use) {
                if (effect.Stats._rawSpecialEffectDataSize == 1) {
                    upTime = effect.GetAverageUptime(0f, 1f, 0, fightDuration2Pass);
                    //float uptime =  (effect.Cooldown / fightDuration);
                    List<SpecialEffect> nestedEffect = new List<SpecialEffect>();
                    nestedEffect.Add(effect.Stats._rawSpecialEffectData[0]);
                    Base.StatsWarrior _stats2 = new Base.StatsWarrior();
                    ApplySpecialEffect(effect.Stats._rawSpecialEffectData[0], charStruct, triggerIntervals, triggerChances, ref _stats2);
                    effectStats = _stats2;
                } else {
                    upTime = effect.GetAverageStackSize(0f, 1f, 0, fightDuration2Pass); 
                }
            } else if (effect.Duration == 0f && triggerIntervals.ContainsKey(effect.Trigger) && !float.IsInfinity(triggerIntervals[effect.Trigger])) {
                upTime = effect.GetAverageProcsPerSecond(triggerIntervals[effect.Trigger], 
                                                         triggerChances[effect.Trigger],
                                                         0,
                                                         fightDuration2Pass);
            } else if (effect.Trigger == Trigger.ExecuteHit) {
                upTime = effect.GetAverageStackSize(triggerIntervals[effect.Trigger], 
                                                         triggerChances[effect.Trigger],
                                                         0,
                                                         fightDuration2Pass * (float)charStruct.BossOpts.Under20Perc);
            } else if (triggerIntervals.ContainsKey(effect.Trigger) && !float.IsInfinity(triggerIntervals[effect.Trigger])) {
                upTime = effect.GetAverageStackSize(triggerIntervals[effect.Trigger], 
                                                         triggerChances[effect.Trigger],
                                                         0,
                                                         fightDuration2Pass);
            }

            if (upTime > 0f) {
                if (effect.Duration == 0f) {
                    applyTo.Accumulate(effectStats, upTime * fightDuration);
                } else if (upTime <= effect.MaxStack) {
                    applyTo.Accumulate(effectStats, upTime);
                }
                return upTime;
            }
            return 0f;
        }

        private static Base.StatsWarrior UpdateStatsAndAdd(Base.StatsWarrior statsToAdd, Base.StatsWarrior baseStats, Character character)
        {
#if DEBUG
            //ConstructionCounts["UpdateStatsAndAdd"]++;
#endif
            Base.StatsWarrior retVal;
            float newStaMult = 1f + statsToAdd.BonusEnduranceMultiplier;
            float newStrMult = 1f + statsToAdd.BonusStrengthMultiplier;
            float newAgiMult = 1f + statsToAdd.BonusCunningMultiplier;
            float newArmMult = 1f + statsToAdd.BonusArmorMultiplier;
            float newBonusArmMult = 1f + statsToAdd.BonusArmorMultiplier;
            float newAtkMult = 1f + statsToAdd.BonusPowerMultiplier;
            float newHealthMult = 1f + statsToAdd.BonusHealthMultiplier;
            if (baseStats != null) {
                retVal = baseStats.Clone();
                
                newStaMult *= (1f + retVal.BonusEnduranceMultiplier);
                newStrMult *= (1f + retVal.BonusStrengthMultiplier);
                newAgiMult *= (1f + retVal.BonusCunningMultiplier);
                newArmMult *= (1f + retVal.BonusArmorMultiplier);
                newBonusArmMult *= (1f + retVal.BonusArmorMultiplier);
                newAtkMult *= (1f + retVal.BonusPowerMultiplier);
                newHealthMult *= (1f + retVal.BonusHealthMultiplier);

                // normalize retVal with its old base stat values, since we're updating them below
                // This essentially reverses what gets done to statsToAdd, but only things that
                // are affected by multipliers (like base stats, armor, AP, etc)
                
                retVal.Health -= retVal.Endurance * 10f; // Stamina is affected by a multiplier

                // Since AP is set to (RawAP + 2*STR + A2T + BonusAP)*APMult, and Str/A2T are affected by mults too,
                // we need to rewind the Str and Armor components out.  We will add them after we've updated Str/Armor, below
                retVal.Power /= 1f + retVal.BonusPowerMultiplier;
                retVal.Power -= (retVal.Strength * 2f);

                    // This is reversing the Armor = (Armor*BaseMult + Bonus)*BonusMult
                retVal.Armor /= 1f + retVal.BonusArmorMultiplier;
                //retVal.BonusArmor -= retVal.Agility * 2f;
            } else { retVal = null; }

            //statsToAdd.DeathbringerProc = 0f;

            #region Base Stats
            statsToAdd.Endurance  *= newStaMult;
            statsToAdd.Strength *= newStrMult;
            statsToAdd.Cunning *= newAgiMult;

            if (retVal != null)
            {
                // change retvals to use the new mults. Combines Stat/=oldMult; Stat*=newMult
                retVal.Endurance *= newStaMult / (1f + retVal.BonusEnduranceMultiplier);
                retVal.Strength *= newStrMult / (1f + retVal.BonusStrengthMultiplier);
                retVal.Cunning *= newAgiMult / (1f + retVal.BonusCunningMultiplier);
            }
            #endregion

            #region Health
            statsToAdd.Health *= newHealthMult;
            statsToAdd.Health += (statsToAdd.Endurance * 10f);
            if (retVal != null)
            {
                // Combines rollback of oldmult and addition of newmult
                retVal.Health *= newHealthMult / (1f + retVal.BonusHealthMultiplier);
                retVal.Health += retVal.Endurance * 10f;
            }
            #endregion

            #region Armor
            //statsToAdd.BonusArmor += statsToAdd.Agility * 2f;
            statsToAdd.Armor = (statsToAdd.Armor * newBonusArmMult) * newArmMult;
            if (retVal != null)
            {
                //retVal.BonusArmor += retVal.Agility * 2f;
                retVal.Armor = (retVal.Armor * newBonusArmMult) * newArmMult;
            }
            #endregion

            #region Attack Power
            // stats to add
            statsToAdd.Power += (statsToAdd.Strength * 2f);
            statsToAdd.Power *= newAtkMult;
            // reset retval
            if (retVal != null) {
                // already rolled back AP's oldmult, so not combining
                retVal.Power += (retVal.Strength * 2f);
                retVal.Power *= newAtkMult;
            }
            #endregion

            // Crit
            //statsToAdd.PhysicalCrit += StatConversion.GetCritFromRating(statsToAdd.CritRating);
            if (retVal != null) {
            }

            // Haste
            statsToAdd.Alacrity = statsToAdd.Alacrity
                                     * (1f + (1f - StatConversion.GetAlacrityModifierFromRating(statsToAdd.AlacrityRating, statsToAdd.Alacrity)))
                                     - 1f;

            // If we're adding two, then return the .Accumulate
            if (retVal != null) {
                retVal.Accumulate(statsToAdd);
                return retVal;
            } else { return statsToAdd; } // Just processing one, not adding two
        }

        #endregion
    }
}
